/*
 * Decoder.h
 *
 *  Created on: 2012/01/18
 *      Author: takedanaoki
 */

#ifndef INCLUDED_NTENGINE_SOUND_DECODER_H
#define INCLUDED_NTENGINE_SOUND_DECODER_H

#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>

namespace NTEngine {
namespace Sound {

class Decoder {
public:
	Decoder( SLEngineItf engine )
	: mObject( NULL ) {
		SLDataLocator_AndroidBufferQueue loc_abq= {
			SL_DATALOCATOR_ANDROIDBUFFERQUEUE, 8
		};
		SLDataFormat_MIME format_mime = {
			SL_DATAFORMAT_MIME,
			SL_ANDROID_MIME_AACADTS,
			SL_CONTAINERTYPE_RAW
		};
		SLDataSource audioSrc = {
			&loc_abq, &format_mime
		};
		SLDataLocator_AndroidSimpleBufferQueue loc_asbq = {
			SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2
		};
		SLDataFormat_PCM format_pcm = {
			SL_DATAFORMAT_PCM,
			1,
			SL_SAMPLINGRATE_44_1,
			SL_PCMSAMPLEFORMAT_FIXED_8,
			SL_PCMSAMPLEFORMAT_FIXED_8,
			SL_SPEAKER_FRONT_CENTER,
			SL_BYTEORDER_LITTLEENDIAN
		};
		SLDataSink audioSnk = {
			&loc_asbq, &format_pcm
		};
		SLresult result;
		// create audio player
		const SLuint32 numInterfaces = 2;
		const SLInterfaceID ids[ numInterfaces ] = {
			SL_IID_ANDROIDBUFFERQUEUESOURCE,
			SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
		};
		const SLboolean req[ numInterfaces ] = {
			SL_BOOLEAN_TRUE,
			SL_BOOLEAN_TRUE,
		};
		// create audio player
		result = ( *engine )->CreateAudioPlayer( engine
			, &mObject
			, &audioSrc, &audioSnk
			, numInterfaces, ids, req
		);
		ASSERT( SL_RESULT_SUCCESS == result );
		result = ( *mObject )->Realize( mObject, SL_BOOLEAN_FALSE );
		ASSERT( SL_RESULT_SUCCESS == result );
		result = ( *mObject )->GetInterface( mObject, SL_IID_PLAY, &mPlay );
		ASSERT( SL_RESULT_SUCCESS == result );

		// android buffer queue
		result = ( *mObject )->GetInterface( mObject, SL_IID_ANDROIDBUFFERQUEUESOURCE, &mLocatorBufferQueue );
		ASSERT( SL_RESULT_SUCCESS == result );
		result = ( *mLocatorBufferQueue )->SetCallbackEventsMask( mLocatorBufferQueue, SL_ANDROIDBUFFERQUEUEEVENT_PROCESSED );
		ASSERT( SL_RESULT_SUCCESS == result );
		result = ( *mLocatorBufferQueue )->RegisterCallback( mLocatorBufferQueue, cbStreamingBufferQueue, this );
		ASSERT( SL_RESULT_SUCCESS == result );
		result = ( *mLocatorBufferQueue )->Enqueue( mLocatorBufferQueue,
			NULL, // pBufferContext
			NULL,
			0,
			NULL,
			0 );
		ASSERT( SL_RESULT_SUCCESS == result );

		// android simple buffer queue
		result = ( *mObject )->GetInterface( mObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &mSinkBufferQueue );
		ASSERT( SL_RESULT_SUCCESS == result );
		result = ( *mSinkBufferQueue )->RegisterCallback( mSinkBufferQueue, cbSimpleBufferQueue, this );
		ASSERT( SL_RESULT_SUCCESS == result );
		result = ( *mSinkBufferQueue )->Enqueue( mSinkBufferQueue, NULL, 0 );
		ASSERT( SL_RESULT_SUCCESS == result );
	}

	~Decoder() {
		if ( mObject ) {
			( *mObject )->Destroy( mObject );
			mObject = NULL;
		}
	}

	void play() {
		const int kCacheSize = BUFFER_SIZE * NB_BUFFERS;
		int bytesRead = ( 1024 < kCacheSize ) ? 1024 : kCacheSize;
		mBufferPos = bytesRead;
		if ( ( bytesRead % MPEG2_TS_PACKET_SIZE ) != 0 ) {
//			LOGV( "Dropping last packet because it is not whole" );
		}
		int packetsRead = bytesRead / MPEG2_TS_PACKET_SIZE;
//		LOGV( "Initially queueing %d packets", packetsRead );

		// Enqueue the content of our cache before starting to play, we don't want to starve the player
		for ( int i = 0; i < NB_BUFFERS && packetsRead > 0; ++i ) {
			// compute size of this buffer
			int packetsThisBuffer = packetsRead;
			if ( packetsThisBuffer > PACKETS_PER_BUFFER ) {
				packetsThisBuffer = PACKETS_PER_BUFFER;
			}
			size_t bufferSize = packetsThisBuffer * MPEG2_TS_PACKET_SIZE;
			SLresult result = ( *mLocatorBufferQueue )->Enqueue( mLocatorBufferQueue,
					NULL, // pBufferContext
					mBuffer + i * BUFFER_SIZE,
					bufferSize,
					NULL,
					0 );
			ASSERT( SL_RESULT_SUCCESS == result );
			packetsRead -= packetsThisBuffer;
		}
	}

	static void SLAPIENTRY cbSimpleBufferQueue( SLAndroidSimpleBufferQueueItf bq, void* context ) {
		// TODO:データがたまったら呼ばれる
		// すなわちEnqueue時にセットしたバッファが満たされる
	}

	static SLresult SLAPIENTRY cbStreamingBufferQueue(
				SLAndroidBufferQueueItf caller,/* input */
				void *pCallbackContext,        /* input */
				void *pBufferContext,          /* input */
				void *pBufferData,             /* input */
				SLuint32 dataSize,             /* input */
				SLuint32 dataUsed,             /* input */
				const SLAndroidBufferItem *pItems,/* input */
				SLuint32 itemsLength           /* input */
		) {
		// TODO:デコードが終わったら呼ばれる
		// ポインタ渡されてる？
		ASSERT( pCallbackContext );
		// bufferContextの判定
		if ( ( pBufferData == NULL ) && ( pBufferContext != NULL ) ) {
			// コールバッファとバッファコンテキストが一緒なら終わり
			if ( pBufferContext == pCallbackContext ) {
//				LOGV( "EOS was processed\n" );
				// our buffer with the EOS message has been consumed
				ASSERT( 0 == dataSize );
				return SL_RESULT_SUCCESS;
			}
		}
		// pBufferData is a pointer to a buffer that we previously Enqueued
		ASSERT( ( dataSize > 0 ) && ( ( dataSize % MPEG2_TS_PACKET_SIZE ) == 0 ) );
		// インターフェースは同じ？
		Decoder* self = static_cast< Decoder* >( pCallbackContext );
		ASSERT( self->mLocatorBufferQueue && self->mLocatorBufferQueue == caller );
		self->onCallBackStreaming();
		return SL_RESULT_SUCCESS;
	}

	void onCallBackStreaming() {
			// don't bother trying to read more data once we've hit EOF
			if ( mReachedEof ) {
				return ;
			}
			if ( 1024 == mBufferPos && mIsLoop ) {
				// 先頭に戻す
				mBufferPos = 0;
			}
			int bytesRead = ( 1024 - mBufferPos < BUFFER_SIZE ) ? ( 1024 - mBufferPos ) : BUFFER_SIZE;
			if ( bytesRead > 0 ) {
				if ( ( bytesRead % MPEG2_TS_PACKET_SIZE ) != 0 ) {
//					LOGV( "Dropping last packet because it is not whole" );
				}
				int packetsRead = bytesRead / MPEG2_TS_PACKET_SIZE;
				int bufferSize = packetsRead * MPEG2_TS_PACKET_SIZE;
				SLresult res = ( *mLocatorBufferQueue )->Enqueue( mLocatorBufferQueue,
						NULL, // pBufferContext
						mBuffer + mBufferPos, // pData
						bufferSize, // dataLength
						NULL, // pMsg
						0 ); // msgLength
				ASSERT( SL_RESULT_SUCCESS == res );
				mBufferPos += bytesRead;
			} else {
				// EOF or I/O error, signal EOS
				SLAndroidBufferItem msgEos[ 1 ];
				msgEos[ 0 ].itemKey = SL_ANDROID_ITEMKEY_EOS;
				msgEos[ 0 ].itemSize = 0;
				// EOS message has no parameters, so the total size of the message is the size of the key
				//   plus the size if itemSize, both XAuint32
				SLresult res = ( *mLocatorBufferQueue )->Enqueue( mLocatorBufferQueue,
						this, // pBufferContext
						NULL, // pData
						0, // dataLength
						msgEos, // pMsg
						sizeof( SLuint32 ) * 2 ); // msgLength
				ASSERT( SL_RESULT_SUCCESS == res );
				mReachedEof = true;
			}
		}

	SLObjectItf mObject;
	SLPlayItf mPlay;
	SLAndroidSimpleBufferQueueItf mSinkBufferQueue;
	SLAndroidBufferQueueItf mLocatorBufferQueue;

	char mBuffer[ 1024 ];
	int mBufferPos; // 現在のバッファポジション
	bool mReachedEof; // 終端に達したか
	// ループ再生用
	bool mIsLoop;

	// simple buffer queue constants
	static const int BUFFER_NUMBER = 2;
	// stream buffer queue constants
	static const int MPEG2_TS_PACKET_SIZE = 188;
	static const int PACKETS_PER_BUFFER = 10;
	static const int BUFFER_SIZE = MPEG2_TS_PACKET_SIZE * PACKETS_PER_BUFFER;
	static const int NB_BUFFERS = 8;
};

} // namespace Sound
} // namespace NTEngine

#endif // INCLUDED_NTENGINE_SOUND_DECODER_H
